using System;
using System.Configuration;
using System.IO;
using System.Reflection;
using System.Runtime.Remoting.Messaging;
using System.Web;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using NHibernate;
using NHibernate.Cache;
using NHibernate.Tool.hbm2ddl;
using Configuration = NHibernate.Cfg.Configuration;

namespace PortalFolia.Repositorio
{
        /// <summary>
        /// Handles creation and management of sessions and transactions.  It is a singleton because 
        /// building the initial session factory is very expensive. Inspiration for this class came 
        /// from Chapter 8 of Hibernate in Action by Bauer and King.  Although it is a sealed singleton
        /// you can use TypeMock (http://www.typemock.com) for more flexible testing.
        /// </summary>
        public sealed class GerenciadorBD
        {
            public static string StringDeConexao = ConfigurationManager.ConnectionStrings["PortalFoliaConnectionString"].ConnectionString;

            #region Thread-safe, lazy Singleton

            /// <summary>
            /// This is a thread-safe, lazy singleton.  See http://www.yoda.arachsys.com/csharp/singleton.html
            /// for more details about its implementation.
            /// </summary>
            public static GerenciadorBD Instance
            {
                get
                {
                    return Nested.NHibernateSessionManager;
                }
            }

            /// <summary>
            /// Initializes the NHibernate session factory upon instantiation.
            /// </summary>
            private GerenciadorBD()
            {
                IniciarFabricaSessao();
            }

            /// <summary>
            /// Assists with ensuring thread-safe, lazy singleton
            /// </summary>
            private class Nested
            {
                static Nested() { }
                internal static readonly GerenciadorBD NHibernateSessionManager =
                    new GerenciadorBD();
            }

            #endregion

            private void IniciarFabricaSessao()
            {
                //sessionFactory = new Configuration().Configure().BuildSessionFactory();

                try
                {
                    Assembly asm = Assembly.LoadFrom(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"bin\PortalFolia.Data.dll"));
                    //Assembly asm = Assembly.LoadFrom(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"Saap.Contabilidade.Dados.dll"));

                    sessionFactory = Fluently.Configure()
                        .Database(MsSqlConfiguration.MsSql2008
                            .ConnectionString(ConfigurationManager.ConnectionStrings["PortalFoliaConnectionString"].ConnectionString))
                                .Mappings(m => m.FluentMappings.AddFromAssembly(asm))
                                    //.ExposeConfiguration(GerarSchema)
                                        .BuildSessionFactory();
                }
                catch (Exception e)
                {
                    var a = e;
                }
            }

            /// <summary>
            /// Allows you to register an interceptor on a new session.  This may not be called if there is already
            /// an open session attached to the HttpContext.  If you have an interceptor to be used, modify
            /// the HttpModule to call this before calling BeginTransaction().
            /// </summary>
            public void RegisterInterceptor(IInterceptor interceptor)
            {
                ISession session = ContextSession;

                if (session != null && session.IsOpen)
                {
                    throw new CacheException("You cannot register an interceptor once a session has already been opened");
                }

                ObterSessao(interceptor);
            }

            public ISession SessaoAtual()
            {
                return ObterSessao(null);
            }

            /// <summary>
            /// Gets a session with or without an interceptor.  This method is not called directly; instead,
            /// it gets invoked from other public methods.
            /// </summary>
            private ISession ObterSessao(IInterceptor interceptor)
            {
                ISession session = ContextSession;

                if (session == null)
                {
                    session = interceptor != null ? sessionFactory.OpenSession(interceptor) : sessionFactory.OpenSession();

                    ContextSession = session;
                }


                return session;
            }

            /// <summary>
            /// Flushes anything left in the session and closes the connection.
            /// </summary>
            public void FecharSessao()
            {
                ISession session = ContextSession;

                if (session != null && session.IsOpen)
                {
                    session.Flush();
                    session.Close();
                }

                ContextSession = null;
            }

            public void IniciarTransacao()
            {
                ITransaction transaction = ContextTransaction;

                if (transaction == null)
                {
                    transaction = SessaoAtual().BeginTransaction();
                    ContextTransaction = transaction;
                }
            }

            public void ConfirmarTransacao()
            {
                ITransaction transaction = ContextTransaction;

                try
                {
                    if (HasOpenTransaction())
                    {
                        transaction.Commit();
                        ContextTransaction = null;
                    }
                }
                catch (HibernateException)
                {
                    CancelarTransacao();
                    throw;
                }
            }

            public bool HasOpenTransaction()
            {
                ITransaction transaction = ContextTransaction;

                return transaction != null && !transaction.WasCommitted && !transaction.WasRolledBack;
            }

            public void CancelarTransacao()
            {
                ITransaction transaction = ContextTransaction;

                try
                {
                    if (HasOpenTransaction())
                    {
                        transaction.Rollback();
                    }

                    ContextTransaction = null;
                }
                finally
                {
                    FecharSessao();
                }
            }

            /// <summary>
            /// If within a web context, this uses <see cref="HttpContext" /> instead of the WinForms 
            /// specific <see cref="CallContext" />.  Discussion concerning this found at 
            /// http://forum.springframework.net/showthread.php?t=572.
            /// </summary>
            private ITransaction ContextTransaction
            {
                get
                {
                    if (IsInWebContext())
                    {
                        return (ITransaction)HttpContext.Current.Items[TRANSACTION_KEY];
                    }
                    else
                    {
                        return (ITransaction)CallContext.GetData(TRANSACTION_KEY);
                    }
                }
                set
                {
                    if (IsInWebContext())
                    {
                        HttpContext.Current.Items[TRANSACTION_KEY] = value;
                    }
                    else
                    {
                        CallContext.SetData(TRANSACTION_KEY, value);
                    }
                }
            }

            /// <summary>
            /// If within a web context, this uses <see cref="HttpContext" /> instead of the WinForms 
            /// specific <see cref="CallContext" />.  Discussion concerning this found at 
            /// http://forum.springframework.net/showthread.php?t=572.
            /// </summary>
            private ISession ContextSession
            {
                get
                {
                    if (IsInWebContext())
                    {
                        return (ISession)HttpContext.Current.Items[SESSION_KEY];
                    }
                    else
                    {
                        return (ISession)CallContext.GetData(SESSION_KEY);
                    }
                }
                set
                {
                    if (IsInWebContext())
                    {
                        HttpContext.Current.Items[SESSION_KEY] = value;
                    }
                    else
                    {
                        CallContext.SetData(SESSION_KEY, value);
                    }
                }
            }

            private bool IsInWebContext()
            {
                return HttpContext.Current != null;
            }

            private static void GerarSchema(Configuration config)
            {
                // HOW TO:
                // First, create a new SQL Server database, "QUOTATIONS".
                // change first parameter of .Create method to true to see schema, second to true to execute the SQL and // create the schema
                // in your database - automapped from your POCO domain.
                // after first run and the database is created, set the second parameter to false 
                // so it doesn't try to recreate the database schema!
                new SchemaExport(config).Create(true, true);
            }

            private const string TRANSACTION_KEY = "CONTEXT_TRANSACTION";
            private const string SESSION_KEY = "CONTEXT_SESSION";
            private ISessionFactory sessionFactory;
        }
}
